﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
using Emgu.CV.UI;


namespace finProj.Entities
{
    public class Picture
    {
        public Image<Bgr, Byte> origPicture;
        public Image<Gray, Byte> picture;
        public Image<Bgr, Byte> detectedFacePic;
        public Image<Gray, Byte> canny;
        public Image<Bgr, Byte> cannyDetected;
        public FeatureVector FeaturesVec;
        public string Name;

        private String filename;


       public Picture(Image<Gray, Byte> toLoad)
       {
           this.picture = toLoad;

       }
       
        public Picture()
       {
           detectedFacePic = new Image<Bgr, Byte>(this.filename);
           detectedFacePic = detectedFacePic.Resize(400, 400, INTER.CV_INTER_LINEAR, true);

           //Convert the image to grayscale and filter out the noise
           picture = detectedFacePic.Convert<Gray, Byte>().PyrDown().PyrUp();
           FeaturesVec = new FeatureVector();

            detectFaceFeatures();

       }

        public Picture(string path)
        {
            //detectedFacePic = new Image<Bgr, Byte>(path);
            origPicture = new Image<Bgr, Byte>(path);

            detectedFacePic = origPicture.Copy();
            //Convert the image to grayscale and filter out the noise
            picture = origPicture.Convert<Gray, Byte>().PyrDown().PyrUp();
            FeaturesVec = new FeatureVector();
        }
        public void findCountours()
        {
            var elList = new List<Ellipse>();
            bool circleFound = false;

            using (MemStorage storage = new MemStorage()) //allocate storage for contour approximation
                for (
                    Contour<Point> contours = canny.FindContours(CHAIN_APPROX_METHOD.CV_LINK_RUNS, RETR_TYPE.CV_RETR_TREE);
                    contours != null;
                    contours = contours.HNext)
                {
                    Contour<Point> currentContour = contours.ApproxPoly(contours.Perimeter*0.05, storage);
                    if (contours.Area > 20) //only consider contours with area greater than 250
                    {
                        if (currentContour.Total >= 4 && (!circleFound))
                        {
                            PointF[] pts = new PointF[currentContour.Total];
                            int count = 0;
                            if (currentContour.Convex && currentContour.Area >= 20)
                            {
                                int _x = 0, _y = 0;

                                foreach (Point p in currentContour.ToArray())
                                {
                                    pts[count++] = new PointF(p.X, p.Y);
                                    _x += p.X;
                                    _y += p.Y;
                                }
                                _x = _x/currentContour.Total;
                                _y = _y/currentContour.Total;
                                bool isCircle = true;
                                foreach (Point p in currentContour.ToArray())
                                {
                                    int _diffx = Math.Abs(_x - p.X);
                                    int _diffy = Math.Abs(_y - p.Y);
                                    if ((_diffx != 0) && (_diffy != 0))
                                    {
                                        int _diffAbs = (int)Math.Sqrt(Math.Pow((double) (_diffx), 2) + Math.Pow((double) (_diffy), 2));
                                        int _diffAbsBase =
                                            (int)
                                            Math.Sqrt(Math.Pow((double) (Math.Abs(currentContour.First().X - _x)), 2) +
                                                      Math.Pow((double) (Math.Abs(currentContour.First().Y - _y)), 2));
                                        if ( ((_diffAbs*1.4) < _diffAbsBase) || ((_diffAbs/1.4) > _diffAbsBase) )
                                        {
                                            isCircle = false;
                                            break;
                                        }
                                    }
                                }
                                if (isCircle)
                                {
                                    elList.Add(Emgu.CV.PointCollection.EllipseLeastSquareFitting(pts));
                                    circleFound = true;
                                    canny.Draw(PointCollection.EllipseLeastSquareFitting(pts),new Gray(255) , 1);
                                    //ImageViewer.Show(canny, String.Format("circles found"));
                                }
                            }


                        }
                    }
                }
        }


        public void DoCanny()
        {
            Gray cannyThreshold = new Gray(10);
            Gray cannyThresholdLinking = new Gray(120);

            this.canny = picture.Canny(cannyThreshold, cannyThresholdLinking);
            this.cannyDetected = canny.Convert<Bgr, byte>();
        }


        private MCvAvgComp[] betterEyesDetection(Image<Gray, Byte> gray)
        {
            int neighbors = 10;
            HaarCascade eye = new HaarCascade("haarcascade_eye.xml");

            MCvAvgComp[] eyesDetected;

            eyesDetected = eye.Detect(gray, 1.1, neighbors, HAAR_DETECTION_TYPE.DO_CANNY_PRUNING, new Size(20, 20));

            while (eyesDetected.Length != 2 && neighbors >=0 )
            {
                neighbors--; 
                eyesDetected = eye.Detect(gray, 1.1, neighbors, HAAR_DETECTION_TYPE.DO_CANNY_PRUNING, new Size(20, 20));
            }

            return eyesDetected;
        }
        private MCvAvgComp[] betterNoseDetection(Image<Gray, Byte> gray)
        {
            int neighbors = 10;
            HaarCascade nose = new HaarCascade("haarcascade_mcs_nose.xml");

            MCvAvgComp[] noseDetected;

            noseDetected = nose.Detect(gray, 1.1, neighbors, HAAR_DETECTION_TYPE.DO_CANNY_PRUNING, new Size(20, 20));

            while (noseDetected.Length != 1 && neighbors >= 0)
            {
                neighbors--;
                noseDetected = nose.Detect(gray, 1.1, neighbors, HAAR_DETECTION_TYPE.DO_CANNY_PRUNING, new Size(20, 20));
            }

            return noseDetected;
        }

        private MCvAvgComp[] betterMouthDetection(Image<Gray, Byte> gray)
        {
            int neighbors = 10;
            HaarCascade mouth = new HaarCascade("haarcascade_mcs_mouth.xml");


            MCvAvgComp[] mouthDetected;
            MCvAvgComp minMouth = new MCvAvgComp();

            mouthDetected = mouth.Detect(gray, 1.1, neighbors, HAAR_DETECTION_TYPE.DO_CANNY_PRUNING, new Size(10, 10));

            //while (mouthDetected.Length != 1 && neighbors >= 0)
            //{
            //    neighbors--;
            //    mouthDetected = mouth.Detect(gray, 1.1, neighbors, HAAR_DETECTION_TYPE.DO_CANNY_PRUNING, new Size(20, 20));
            //

            while (neighbors > 1)
            {
                if (mouthDetected.Length == 1 && minMouth.rect.Width == 0 && minMouth.rect.Height == 0)
                {
                    minMouth = mouthDetected[0];
                }
                if (mouthDetected.Length == 1 && minMouth.rect.Width != 0 && minMouth.rect.Height != 0)
                {
                    if (mouthDetected[0].rect.Width + mouthDetected[0].rect.Height < minMouth.rect.Width + minMouth.rect.Height)
                    {
                        minMouth = mouthDetected[0];
                    }
                    
                }
                neighbors--;
                mouthDetected = mouth.Detect(gray, 1.1, neighbors, HAAR_DETECTION_TYPE.DO_CANNY_PRUNING,
                                             new Size(10, 10));
            }


           MCvAvgComp[] detected = new MCvAvgComp[1];
            detected[0] = minMouth;
            return detected;
        }

        public bool detectFeatures()
        {
            try
            {
                DoCanny();

                Image<Gray, Byte> gray = picture;
                //gray._EqualizeHist();

                Stopwatch watch = Stopwatch.StartNew();

                //HaarCascade face = new HaarCascade("haarcascade_frontalface_alt_tree.xml");
                //HaarCascade eye = new HaarCascade("haarcascade_eye.xml");
                //HaarCascade mouth = new HaarCascade("haarcascade_mcs_mouth.xml");
                HaarCascade nose = new HaarCascade("haarcascade_mcs_nose.xml");

                MCvAvgComp[] eyesDetected = betterEyesDetection(gray);
                       
                MCvAvgComp leftEyefull;
                MCvAvgComp rightEyefull;
                        
                Rectangle leftEye = new Rectangle(0,0,0,0);
                Rectangle rightEye = new Rectangle(0, 0, 0, 0);

                if (eyesDetected.Length == 2)
                {
                    leftEyefull = eyesDetected[0].rect.X < eyesDetected[1].rect.X
                                        ? eyesDetected[0]
                                        : eyesDetected[1];
                    leftEye = leftEyefull.rect;
                    //leftEye.Offset(f.rect.X, f.rect.Y);

                    rightEyefull = eyesDetected[0].rect.X > eyesDetected[1].rect.X
                                    ? eyesDetected[0]
                                    : eyesDetected[1];
                    rightEye = rightEyefull.rect;
                    //rightEye.Offset(f.rect.X, f.rect.Y);

                    gray.ROI = Rectangle.Empty;

                    double dx = rightEye.X - (leftEye.X + leftEye.Width);
                    double dy = rightEye.Y + 0.5 * rightEye.Height -
                                        (leftEye.Y + 0.5 * leftEye.Height);

                    double d = Math.Sqrt(Math.Pow(dx, 2) + Math.Pow(dy, 2));
                    FeaturesVec.features.Add("InnerEyeDistance", d);

                    dx = rightEye.X + rightEye.Width - leftEye.X;
                    dy = rightEye.Y + 0.5 * rightEye.Height -
                            (leftEye.Y + 0.5 * leftEye.Height);

                    d = Math.Sqrt(Math.Pow(rightEye.X + rightEye.Width - leftEye.X, 2) +
                                Math.Pow(rightEye.Y + 0.5 * rightEye.Height -
                                        (leftEye.Y + 0.5 * leftEye.Height), 2));
                    FeaturesVec.features.Add("OuterEyeDistance", d);

                    foreach (MCvAvgComp e in eyesDetected)
                    {
                        Rectangle eyeRect = e.rect;
                        //eyeRect.Offset(f.rect.X, f.rect.Y);
                        detectedFacePic.Draw(eyeRect, new Bgr(Color.Red), 1);
                        cannyDetected.Draw(eyeRect, new Bgr(Color.Red), 1);

                    }
                }
                else
                {
                    return false;
                }
                gray.ROI = new Rectangle(0, ((leftEye.Y + rightEye.Y)/2), gray.Width, gray.Height);
                //MCvAvgComp[] noseDetected = nose.Detect(gray, 1.1, 10, HAAR_DETECTION_TYPE.DO_CANNY_PRUNING, new Size(20, 20));
                MCvAvgComp[] noseDetected = betterNoseDetection(gray);
                gray.ROI = Rectangle.Empty;
                Rectangle noseRect;

                if (noseDetected.Length == 1)
                {
                    noseRect = noseDetected[0].rect;
                    noseRect.Offset(0, ((leftEye.Y + rightEye.Y)/2));
                    //noseRect.Offset(0, f.rect.Y);
                    detectedFacePic.Draw(noseRect, new Bgr(Color.LawnGreen), 1);
                    cannyDetected.Draw(noseRect, new Bgr(Color.LawnGreen), 1);

                    if (eyesDetected.Length == 2)
                    {
                        double d =
                            Math.Sqrt(Math.Pow(noseRect.X + 0.5 * noseRect.Width - leftEye.X, 2) +
                                        Math.Pow((noseRect.Y + 0.5 * noseRect.Height) -
                                                (leftEye.Y + 0.5 * leftEye.Height), 2));
                        FeaturesVec.features.Add("Nose-LeftEye", d);

                        d = Math.Sqrt(Math.Pow(rightEye.X + rightEye.Width - (noseRect.X + 0.5 * noseRect.Width), 2) +
                                        Math.Pow((noseRect.Y + 0.5 * noseRect.Height) -
                                                (rightEye.Y + 0.5 * rightEye.Height), 2));
                        FeaturesVec.features.Add("Nose-RightEye", d);
                    }
                }
                else
                    return false;

                gray.ROI = new Rectangle(0, 2 * gray.Height / 3, gray.Width, gray.Height / 3);
                //MCvAvgComp[] mouthDetected = mouth.Detect(gray, 1.3, 10, HAAR_DETECTION_TYPE.DO_CANNY_PRUNING, new Size(10, 10));
                MCvAvgComp[] mouthDetected = betterMouthDetection(gray);
                gray.ROI = Rectangle.Empty;

                if (mouthDetected.Length == 1)
                {
                    foreach (MCvAvgComp e in mouthDetected)
                    {
                        Rectangle mRect = e.rect;
                        mRect.Offset(0, 2 * gray.Height / 3);
                        detectedFacePic.Draw(mRect, new Bgr(Color.Pink), 1);
                        cannyDetected.Draw(mRect, new Bgr(Color.Pink), 1);
                        if (eyesDetected.Length == 2)
                        {
                            double d =
                            Math.Sqrt(Math.Pow(mRect.X + 0.5 * mRect.Width - leftEye.X, 2) +
                                        Math.Pow((mRect.Y + 0.5 * mRect.Height) -
                                                (leftEye.Y + 0.5 * leftEye.Height), 2));
                            FeaturesVec.features.Add("Mouth-LeftEye", d);

                            d = Math.Sqrt(Math.Pow(rightEye.X + rightEye.Width - (mRect.X + 0.5 * mRect.Width), 2) +
                                            Math.Pow((mRect.Y + 0.5 * mRect.Height) -
                                                    (rightEye.Y + 0.5 * rightEye.Height), 2));
                            FeaturesVec.features.Add("Mouth-RightEye", d);
                        }
                        if (noseDetected.Length == 1)
                        {
                            double d =
                            Math.Sqrt(Math.Pow(noseRect.Y - mRect.Y, 2) +
                                        Math.Pow((noseRect.X + 0.5 * noseRect.Width) -
                                                (mRect.X + 0.5 * mRect.Width), 2));
                            FeaturesVec.features.Add("Nose-Mouth", d);

                        }
                    }
                }
                else
                    return false;

            }
            catch (TypeInitializationException te)
            {
                Console.WriteLine(te.Message);
                throw;
            }
            return true;
        }


        public void detectFace()
        {
            Image<Gray, Byte> gray = picture;
            //picture._EqualizeHist();

            Stopwatch watch = Stopwatch.StartNew();

            HaarCascade face = new HaarCascade("haarcascade_frontalface_alt_tree.xml");
            MCvAvgComp[] facesDetected = face.Detect(gray,1.1,20,HAAR_DETECTION_TYPE.DO_CANNY_PRUNING, new Size(20,20));
            if (facesDetected.Length == 1)
            {
                Rectangle rect = facesDetected[0].rect;
                rect.X -= 15;
                rect.Y -= 15;
                rect.Width += 15;
                rect.Height += 15;
                gray.ROI = rect;
                this.detectedFacePic.ROI = facesDetected[0].rect;
                this.origPicture.ROI = facesDetected[0].rect;

                this.detectedFacePic = this.detectedFacePic.Copy();
                this.origPicture = this.origPicture.Copy();
                this.picture = gray.Copy();
               
                gray = this.picture;

            }
            watch.Stop();
        }

        public void detectFaceFeatures()
        {
            try
            {
                DoCanny();

                Image<Gray, Byte> gray = picture;
                picture._EqualizeHist();

                Stopwatch watch = Stopwatch.StartNew();
                
                HaarCascade face = new HaarCascade("haarcascade_frontalface_alt_tree.xml");
                HaarCascade eye = new HaarCascade("haarcascade_eye.xml");
                HaarCascade mouth = new HaarCascade("haarcascade_mcs_mouth.xml");
                HaarCascade nose = new HaarCascade("haarcascade_mcs_nose.xml");

                MCvAvgComp[] facesDetected = face.Detect(picture,1.1,10,HAAR_DETECTION_TYPE.DO_CANNY_PRUNING, new Size(20,20));
                if (facesDetected.Length == 1 )
                {
                    foreach (MCvAvgComp f in facesDetected)
                    {
                        detectedFacePic.Draw(f.rect, new Bgr(Color.Blue), 1);
                        cannyDetected.Draw(f.rect, new Bgr(Color.Blue), 1);

                        //Set the region of interest on the faces
                        picture.ROI = f.rect;
                        MCvAvgComp[] eyesDetected = eye.Detect(picture, 1.1, 10, HAAR_DETECTION_TYPE.DO_CANNY_PRUNING, new Size(20, 20));

                        MCvAvgComp leftEyefull;
                        MCvAvgComp rightEyefull;

                        leftEyefull = eyesDetected[0].rect.X < eyesDetected[1].rect.X
                                            ? eyesDetected[0]
                                            : eyesDetected[1];
                        Rectangle leftEye = leftEyefull.rect;
                        leftEye.Offset(f.rect.X, f.rect.Y);

                        rightEyefull = eyesDetected[0].rect.X > eyesDetected[1].rect.X
                                        ? eyesDetected[0]
                                        : eyesDetected[1];
                        Rectangle rightEye = rightEyefull.rect;
                        rightEye.Offset(f.rect.X, f.rect.Y);

                        picture.ROI = Rectangle.Empty;

                        if (eyesDetected.Length == 2)
                        {

                            double dx = rightEye.X - (leftEye.X + leftEye.Width);
                            double dy = rightEye.Y + 0.5*rightEye.Height -
                                                (leftEye.Y + 0.5*leftEye.Height);

                            double d = Math.Sqrt(Math.Pow(dx, 2) + Math.Pow(dy, 2));
                            FeaturesVec.features.Add("InnerEyeDistance", d);

                            dx = rightEye.X + rightEye.Width - leftEye.X;
                            dy = rightEye.Y + 0.5*rightEye.Height -
                                 (leftEye.Y + 0.5*leftEye.Height);

                            d = Math.Sqrt(Math.Pow(rightEye.X + rightEye.Width - leftEye.X, 2 ) +
                                       Math.Pow(rightEye.Y + 0.5 * rightEye.Height -
                                                (leftEye.Y + 0.5 * leftEye.Height), 2));
                            FeaturesVec.features.Add("OuterEyeDistance", d);

                            foreach (MCvAvgComp e in eyesDetected)
                            {
                                Rectangle eyeRect = e.rect;
                                eyeRect.Offset(f.rect.X, f.rect.Y);
                                detectedFacePic.Draw(eyeRect, new Bgr(Color.Red), 1);
                                cannyDetected.Draw(eyeRect, new Bgr(Color.Red), 1);

                            }
                        }

                        //Set the region of interest on the faces
                        picture.ROI = f.rect;
                        MCvAvgComp[] noseDetected = nose.Detect(picture, 1.1, 10, HAAR_DETECTION_TYPE.DO_CANNY_PRUNING, new Size(20, 20));
                        picture.ROI = Rectangle.Empty;

                        if (noseDetected.Length == 1)
                        {
                            Rectangle noseRect = noseDetected[0].rect;
                            noseRect.Offset(f.rect.X, f.rect.Y);
                            detectedFacePic.Draw(noseRect, new Bgr(Color.LawnGreen), 1);
                            cannyDetected.Draw(noseRect, new Bgr(Color.LawnGreen), 1);

                            if (eyesDetected.Length == 2)
                            {
                                double d =
                                    Math.Sqrt(Math.Pow(noseRect.X + 0.5 * noseRect.Width - leftEye.X, 2) +
                                              Math.Pow((noseRect.Y + 0.5 * noseRect.Height) -
                                                       (leftEye.Y + 0.5 * leftEye.Height), 2));
                                FeaturesVec.features.Add("Nose-LeftEye", d);

                                d = Math.Sqrt(Math.Pow(rightEye.X + rightEye.Width - (noseRect.X + 0.5 * noseRect.Width), 2) +
                                             Math.Pow((noseRect.Y + 0.5 * noseRect.Height) -
                                                      (rightEye.Y + 0.5 * rightEye.Height), 2));
                                FeaturesVec.features.Add("Nose-RightEye", d);
                            }

                        }
                       
                        picture.ROI = new Rectangle(f.rect.X, f.rect.Y+2*f.rect.Height/3, f.rect.Width, f.rect.Height/3);
                        MCvAvgComp[] mouthDetected = mouth.Detect(picture, 1.3, 10, HAAR_DETECTION_TYPE.DO_CANNY_PRUNING, new Size(10, 10));
                        picture.ROI = Rectangle.Empty;

                        if (mouthDetected.Length == 1)
                        {
                            foreach (MCvAvgComp e in mouthDetected)
                            {
                                Rectangle mRect = e.rect;
                                mRect.Offset(f.rect.X, f.rect.Y + 2 * f.rect.Height / 3);
                                detectedFacePic.Draw(mRect, new Bgr(Color.Pink), 1);
                                cannyDetected.Draw(mRect, new Bgr(Color.Pink), 1);

                                if (eyesDetected.Length == 2)
                                {
                                    double d =
                                    Math.Sqrt(Math.Pow(mRect.X + 0.5 * mRect.Width - leftEye.X, 2) +
                                              Math.Pow((mRect.Y + 0.5 * mRect.Height) -
                                                       (leftEye.Y + 0.5 * leftEye.Height), 2));
                                    FeaturesVec.features.Add("Mouth-LeftEye", d);

                                    d = Math.Sqrt(Math.Pow(rightEye.X + rightEye.Width - (mRect.X + 0.5 * mRect.Width), 2) +
                                                 Math.Pow((mRect.Y + 0.5 * mRect.Height) -
                                                          (rightEye.Y + 0.5 * rightEye.Height), 2));
                                    FeaturesVec.features.Add("Mouth-RightEye", d);
                                }
                            }

                        }
                       
                }
            }   
        }
        catch (TypeInitializationException te)
        {
            Console.WriteLine(te.Message);
            throw;
        }
        }



    }
}